import fg from "fast-glob";
import fs from "fs-extra";
import path from "path";
import sizeOf from "image-size";
import { toolchain } from "../toolchain.js";
import { UnityHelper } from "./UnityHelper.js";
import { standardPath } from "./vendor.js";
import normalizePath from "normalize-path";
import _ from "lodash";

const enum ERefStatus {
    None = '',
    Exclusive = '√',
    Bad = '*'
}

interface IImgInfo {
    guid: string
    width: number
    height: number
    size: number
    refers: string[]
}

interface IDirInfo {
    path: string
    imgs: string[]
    totalSize: number
    referedPrefabs: string[]
}

interface IPrefabDirRef {
    path: string
    imgs: string[]
}

interface ISplitSolution {
    prefab: string
    imgs: string[]
}

interface IPrefabDirRefReport extends IPrefabDirRef {
    cntPct: number
    cntPctDesc: string
    sizePct: number
    status: ERefStatus
}

interface IUIRefReport {
    prefab: string
    refs: IPrefabDirRefReport[]
    score: number
}

export class ImageSorter {
    private readonly refStatusDescMap: Record<ERefStatus, string> = {
        [ERefStatus.None]: '',
        [ERefStatus.Exclusive]: '√',
        [ERefStatus.Bad]: '*'
    };

    public async sortAtlas(): Promise<void> {
    //     const allFormOutFile = `tmp/${toolchain.opts.projectName}.imageSorter.allForm.txt`;
    //     await fs.ensureFile(allFormOutFile);
    //     await fs.writeFile(allFormOutFile, '', 'utf-8');

    //     await UIRelations.build();
    //     const tabForms = UIRelations.getAllForms();
    //     tabForms.forEach(v => {
    //         fs.appendFileSync(allFormOutFile, `${v.tsClass.className}: ${v.children.map(v => v.tsClass.className).join(', ')}\n`);
    //     })
    // }

    // private async ttt(): Promise<void> {
        const dirRefOutFile = `tmp/${toolchain.opts.projectName}.imageSorter.dirRef.txt`;
        const uiRefOutFile = `tmp/${toolchain.opts.projectName}.imageSorter.uiRef.txt`;
        const solutionOutFile = `tmp/${toolchain.opts.projectName}.imageSorter.solution.txt`;
        if (!toolchain.opts.output)
        {
            await fs.ensureFile(dirRefOutFile);
            await fs.writeFile(dirRefOutFile, '', 'utf-8');
            await fs.ensureFile(uiRefOutFile);
            await fs.writeFile(uiRefOutFile, '', 'utf-8');
            await fs.ensureFile(solutionOutFile);
            await fs.writeFile(solutionOutFile, '', 'utf-8');
        }

        const infoMap: Record<string, IImgInfo> = {};

        // collect image infomations
        const imgRoot = path.join(toolchain.opts.projectRoot, 'Assets/AssetSources');
        const imgs = await fg(['**/*.png', '**/*.jpg'], { cwd: imgRoot });
        for (const img of imgs) {
            // if (img.includes('miZang512')) {
            //     console.log('aaa');
            // }
            if (!img.startsWith('ui') && !img.startsWith('images')) continue;

            const file = path.join(imgRoot, img);

            const guid = await UnityHelper.readGUID(file + '.meta');

            // get dimensions
            const dimensions = await sizeOf(file);
            if (dimensions.width == null || dimensions.height == null) {
                console.error('get dimensions failed: ', img);
                continue;
            }
            
            const info: IImgInfo = { guid, width: dimensions.width, height: dimensions.height, size: dimensions.width * dimensions.height, refers: [] };
            infoMap[file] = info;
        }

        // get references
        const uiRoot = path.join(toolchain.opts.projectRoot, 'Assets/AssetSources');
        const referMap = await UnityHelper.collectUsedGUID(uiRoot);

        // turn to Record<dir, IDirInfo>, Record<ui, dirs>
        const dirMap: Record<string, IDirInfo> = {}, ui2dirs: Record<string, IPrefabDirRef[]> = {};
        for (const img in infoMap) {
            const dir = path.dirname(img);

            // if (img.includes('miZang512')) {
            //     console.log('aaa');
            // }

            // dir map
            let dinfo = dirMap[dir];
            if (dinfo == null) {
                dirMap[dir] = dinfo = { path: dir, imgs: [], totalSize: 0, referedPrefabs: [] };
            }
            dinfo.imgs.push(img);
            dinfo.totalSize += infoMap[img].size;

            // ui map
            const info = infoMap[img];
            const refers = referMap[info.guid];
            if (refers == null) {
                continue;
            }
            for (const ui of refers) {
                if (!dinfo.referedPrefabs.includes(ui)) dinfo.referedPrefabs.push(ui);

                let dirs = ui2dirs[ui];
                if (dirs == null) {
                    ui2dirs[ui] = dirs = [];
                }
                const d = dirs.find(v => v.path == dir);
                if (d != null) {
                    d.imgs.push(img);
                } else {
                    dirs.push({ path: dir, imgs: [img] });
                }
            }
        }
        const allLowerCaseDirs = Object.keys(dirMap).map(v => v.toLowerCase());

        // check ui
        const uiReports: IUIRefReport[] = [];
        for (const ui in ui2dirs) {
            const dirs = ui2dirs[ui];
            if (dirs.length <= 0) continue;

            const referDirs: IPrefabDirRefReport[] = [];
            for (const d of dirs) {
                // if (!d.path.includes('atlas\\common_')) {
                    let s = 0;
                    d.imgs.forEach(v => s += infoMap[v].size);
                    const dinfo = dirMap[d.path];
                    const sizePct = Math.round(s / dinfo.totalSize * 100);
                    let status = ERefStatus.None;
                    if (dinfo.referedPrefabs.length == 1) {
                        // 专属目录
                        status = ERefStatus.Exclusive;
                    } else if (sizePct < 30) {
                        status = ERefStatus.Bad;
                    }
                    referDirs.push({
                        ...d,
                        cntPct: d.imgs.length / dinfo.imgs.length,
                        cntPctDesc: `${d.imgs.length}/${dinfo.imgs.length}`,
                        sizePct,
                        status
                    });
                // }
            }

            if (referDirs.length > 0) {
                const score = _.meanBy(referDirs, 'sizePct');
                uiReports.push({ prefab: path.basename(ui, '.prefab'), refs: referDirs, score });
            }
        }
        uiReports.sort((a, b) => a.score - b.score);
        if (!toolchain.opts.output) {
            uiReports.forEach(v => {
                const refLines = v.refs.map(v => `  ${this.refStatusDescMap[v.status]} ${normalizePath(path.relative(imgRoot, v.path))}(${v.cntPctDesc}, ${v.sizePct}%): ${v.imgs.map(v => path.basename(v)).join(', ')}`);
                fs.appendFileSync(uiRefOutFile, `${v.prefab} (${v.refs.length}): \n${refLines.join('\n')}\n`);
            });
        }

        // check dirs
        const dirReports: IDirInfo[] = [];
        for (const dir in dirMap) {
            // if (dir.includes('atlas\\common_')) {
            //     continue;
            // }
            const dinfo = dirMap[dir];
            if (dinfo.referedPrefabs.length > 1) {
                dirReports.push(dinfo);
            }
        }
        dirReports.sort((a, b) => b.referedPrefabs.length - a.referedPrefabs.length);

        if (!toolchain.opts.output) {
            dirReports.forEach(d => {
                fs.appendFileSync(dirRefOutFile, `${normalizePath(path.relative(imgRoot, d.path))} (${d.referedPrefabs.length}): ${d.referedPrefabs.map(v => path.basename(v, '.prefab')).join(', ')}\n`);
            });
        }

        // suggestion

        // // 检查是否被多个ui使用（相互不重叠），这样可以直接拆分
        // const splitMap: Record<string, string> = {};
        // for (const dir in dirMap) {
        //     if (dir.includes('atlas\\common_')) {
        //         continue;
        //     }
        //     const dinfo = dirMap[dir];
        //     if (dinfo.referedPrefabs.length > 1) {
        //         for (const p of dinfo.referedPrefabs) {
        //             const uiDirs = ui2dirs[p];
        //             const ud = uiDirs.find(v => v.path == dir);
        //             for (const img of ud!.imgs) {
        //                 if (splitMap[img] == null) {
        //                     splitMap[img] = p;
        //                 } else {
        //                     splitMap[img] = '__SHARED__';
        //                 }
        //             }
        //         }
        //     }
        // }

        // // 生成split solution
        // const splitSolutions: ISplitSolution[] = [];
        // for (const img in splitMap) {
        //     const prefab = splitMap[img];
        //     if (prefab == '__SHARED__') {
        //         continue;
        //     }

        //     let solution = splitSolutions.find(v => v.prefab == prefab);
        //     if (solution == null) {
        //         solution = { prefab, imgs: [] };
        //         splitSolutions.push(solution);
        //     }
        //     solution.imgs.push(img);
        // }

        // // 执行split solution
        // for (const ss of splitSolutions) {
        //     const prefabName = path.basename(ss.prefab, '.prefab');
        //     const uiDirs = ui2dirs[ss.prefab];
        //     const td = uiDirs.find(v => dirMap[v.path].referedPrefabs.length == 1);
        //     let target = '', newTarget = false;
        //     if (td) {
        //         target = td.path;
        //     } else {
        //         const tryDir = path.join(specialRoot, prefabName);
        //         target = tryDir;
        //         let tryCnt = 0;
        //         while (allLowerCaseDirs.includes(target.toLowerCase())) {
        //             target = tryDir + tryCnt;
        //             tryCnt++;
        //         }
        //         newTarget = true;
        //     }
        //     for (const img of ss.imgs) {
        //         const bdn = path.basename(path.dirname(img));
        //         if (bdn.toLowerCase() == prefabName.toLowerCase()) {
        //             continue;
        //         }
        //         if (newTarget) {
        //             await fs.appendFile(solutionOutFile, `mkdir "${standardPath(target)}"\n`);
        //             await fs.appendFile(solutionOutFile, `svn add "${standardPath(target)}"\n`);
        //             newTarget = false;
        //         }
        //         await fs.appendFile(solutionOutFile, `svn mv "${standardPath(img)}" "${standardPath(path.join(target, path.basename(img)))}"\n`);
        //     }
        // }

        // output
        if (toolchain.opts.output) {
            await fs.ensureDir(toolchain.opts.output);
            let op = toolchain.opts.output;
            if (!path.isAbsolute(op)) {
                op = path.join(toolchain.opts.projectRoot, op);
            }

            uiReports.forEach(v => {
                v.refs.forEach(r => {
                    r.path = normalizePath(standardPath(r.path));
                    r.imgs = r.imgs.map(i => normalizePath(standardPath(i)));
                })
            });
            const uiRefOutJson = path.join(op, '__uiRef.json');
            await fs.writeJSON(uiRefOutJson, { uis: uiReports });

            dirReports.forEach(v => {
                v.path = normalizePath(standardPath(v.path));
                v.imgs = v.imgs.map(i => normalizePath(standardPath(i)));
                v.referedPrefabs = v.referedPrefabs.map(r => normalizePath(standardPath(r)));
            });
            const dirRefOutJson = path.join(op, '__dirRef.json');
            await fs.writeJSON(dirRefOutJson, { dirs: dirReports });
        }
    }
}